package com.skilltrack.filter;


import com.skilltrack.config.AuthProperties;
import com.skilltrackedu.constants.UserConstants;
import com.skilltrackedu.exception.enums.AuthError;
import com.skilltrackedu.exception.exception.AuthException;
import com.skilltrackedu.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;

import org.springframework.core.Ordered;

import org.springframework.http.server.reactive.ServerHttpRequest;

import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;




/**
 * @author LENOVO
 * @version 1.0
 * @date 2025/4/17 16:03
 */
@Component
@RequiredArgsConstructor
public class LoginGlobalFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties;

    private final JwtTool jwtTool;

    // 路径匹配器,用于判断请求路径是否匹配可放行路径
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();


    /**
     * 自定义过滤器实现，用于处理进入网关的每个请求
     * 主要功能包括：排除特定请求、验证请求头中的token、解析token获取用户信息、将用户信息添加到请求头中
     *
     * @param exchange 服务器Web交换对象，包含请求和响应的所有信息
     * @param chain    网关过滤器链，用于将控制权传递给下一个过滤器
     * @return Mono<Void> 表示异步处理完成的信号
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        //判断是否是要被拦截的请求,如果是,直接放行
        String path = exchange.getRequest().getPath().toString();
        if (isEXclude(path)) {
            return chain.filter(exchange);
        }
        //如果不是,则获取请求头中的token并校验

        try {
            //获取token并解析token中的用户id和角色
            String token = request.getHeaders().get(UserConstants.AUTHORIZATION_HEADER).get(0);

            //解析token,获得用户id和角色
            Map<String, Object> map = jwtTool.parseToken(token);
            String userId = map.get(UserConstants.USER_ID_KEY).toString();
            String roles = map.get(UserConstants.ROLES_KEY).toString();


            /**
             *      这里要区分角色
             *      如果是去user服务的请求,就把角色写进请求头中发过去,去那边的服务判断
             *      或者直接发过去user服务,通过请求url/user/student/...里面的student/来判断权限是是学生(这一步接口要定义规范
             *
             *
             *      但是所有的转发都要先判断会不会出现 token解析出来的角色是学生,但是这条请求url中没有student/,而是是teacher/的情况
             *      所以这里要判断一下,请求url与token解析出的必须是一致,否则就直接抛token权限异常
             *
             *      然后就定义这个isRoleMatchedWithPath方法去把token解析出来的角色和url中的角色进行匹配
             *      而匹配的标准就是定义在authProperties.yaml文件中的role-access配置,这里已经定义好不同的角色与路径的映射关系
             *
             */

            //判断角色权限与url是否一致(符合配置文件中定义的角色与路径的映射关系)
            if (!isRoleMatchedWithPath(roles, path)) {
                AuthException.cast(AuthError.FORBIDDEN);
            }


            //通过mutate方法修改当前的ServerWebExchange实例，使用lambda表达式设置请求头，携带用户信息
            ServerWebExchange newExchange = exchange.mutate()
                    .request(builder ->
                            builder.header(UserConstants.USER_INFO_USER_ID, userId)
                                    .header(UserConstants.USER_INFO_ROLES, roles)
                    )
                    .build();

            //放行给下一个拦截器
            //让请求继续在拦截器链中传递，直到所有拦截器处理完毕
            return chain.filter(newExchange);
        } catch (Exception e) {
            AuthException.cast(AuthError.INVALID_TOKEN);
        }
        return Mono.empty();//理论不会到这里,只是为了不报错
    }

    /**
     * 检查用户角色是否匹配给定路径
     * 该方法用于验证用户的角色是否有权限访问指定的路径
     * 它通过比较用户的角色和路径与预设的映射关系来确定访问权限
     *
     * @param roles 用户的角色，多个角色以逗号分隔
     * @param path  需要访问的路径
     * @return 如果用户角色匹配路径，则返回true；否则返回false
     */
    private boolean isRoleMatchedWithPath(String roles, String path) {
        //遍历配置文件中的角色与路径的映射关系(map)
        Map<String, List<String>> roleAccess = authProperties.getRoleAccess();
        //循环遍历角色访问权限映射
        for (Map.Entry<String, List<String>> entry : roleAccess.entrySet()) {
            String role = entry.getKey();
            //循环遍历每个角色对应的路径列表
            for (String rolePath : entry.getValue()) {
                //如果角色与路径匹配成功，返回true
                if (role.equals(roles) && antPathMatcher.match(rolePath, path)) {
                    return true;
                }
            }
        }
        //如果没有找到匹配的角色与路径的映射关系，返回false
        return false;
    }

    /**
     * 判断给定路径是否在排除列表中
     * 此方法用于检查传入的路径是否匹配配置中的任何排除路径模式
     * 利用AntPathMatcher来支持模式匹配，这样可以灵活地指定排除路径的模式
     *
     * @param path 待检查的路径
     * @return 如果路径匹配排除列表中的任何一个模式，则返回true；否则返回false
     */
    private boolean isEXclude(String path) {
        // 获取配置中指定的所有排除路径模式
        List<String> excludePaths = authProperties.getExcludePaths();
        for (String excludePath : excludePaths) {
            // 使用AntPathMatcher根据模式检查当前路径是否匹配
            if (antPathMatcher.match(excludePath, path)) {
                // 如果匹配成功，则表明当前路径应被排除，返回true
                return true;
            }
        }
        // 如果没有找到匹配的排除模式，返回false
        return false;
    }


    /**
     * 获取当前对象的顺序值
     * <p>
     * 顺序值用于确定对象在某些操作中的执行顺序或显示顺序
     * 返回值为0表示默认顺序，具体数值的含义依赖于具体的业务逻辑和使用场景
     *
     * @return int 返回当前对象的顺序值，0表示默认顺序
     */
    @Override
    public int getOrder() {
        //值越小则优先级越高
        return 0;
    }
}